package gameStates.network;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import networking.MasterServerInterface;
import networking.ServerSyncManager;
import networking.SyncManager;
import networking.Synchronizable;
import networking.messages.ChatMessage;
import networking.messages.GameStartMessage;
import networking.messages.HunterRegisterMessage;
import networking.messages.JoinMessage;
import networking.messages.PingMessage;
import networking.messages.SimpleMessage;
import networking.messages.SquadUpdateMessage;
import networking.messages.TimeSyncMessage;
import networking.messages.TimeSyncMessage.TimeType;
import networking.messages.createMessages.MSInfoMessage;
import networking.messages.playerMessages.PlayerAckMessage;
import networking.messages.playerMessages.PlayerAddMessage;
import networking.messages.playerMessages.PlayerAwardMessage;
import networking.messages.playerMessages.PlayerKillMessage;
import networking.messages.playerMessages.PlayerRegisterMessage;
import networking.messages.playerMessages.PlayerUpdateMessage;
import networking.messages.playerMessages.PlayerUpdateValueType;
import networking.messages.playerMessages.TeamChangeAnswerMessage;
import networking.messages.playerMessages.TeamChangeWantMessage;
import networking.messages.syncMessages.CMUseMessage;
import networking.messages.syncMessages.CollisionDamageMessage;
import networking.messages.syncMessages.NearStationChangeMessage;
import networking.messages.syncMessages.NodeSyncMessage;
import networking.messages.syncMessages.SpawnStationSyncMessage;
import networking.messages.syncMessages.UpgradeMessage;
import networking.messages.syncMessages.ValueType;
import networking.messages.teamMessages.ProcessUpdateMessage;
import networking.messages.teamMessages.TeamValueMessage;
import networking.messages.weaponMessages.WeaponAddMessage;
import networking.messages.weaponMessages.WeaponFireAddMessage;
import networking.messages.weaponMessages.WeaponFireRemoveMessage;
import networking.messages.weaponMessages.WeaponRemoveMessage;
import networking.messages.weaponMessages.WeaponRequestMessage;

import gameStates.GameTimeListener;
import gameStates.absGamesStates.network.AbsServerState;
import interfaces.hud.chat.ChatType;

import com.captiveimagination.jgn.clientserver.JGNConnection;
import com.captiveimagination.jgn.message.Message;
import com.captiveimagination.jgn.synchronization.message.SynchronizeCreateMessage;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.util.GameTaskQueueManager;

import logic.common.Squad;
import logic.common.SquadChangedEvent;
import logic.common.SquadListener;
import logic.common.player.Player;
import logic.common.player.awards.PlayerAwardListener;
import logic.common.player.awards.PlayerGotAwardEvent;
import logic.common.player.playerListeners.KillEvent;
import logic.common.player.playerListeners.NearStationChangedEvent;
import logic.common.player.playerListeners.TargetChangedEvent;
import logic.common.team.Fraction;
import logic.common.team.Team;
import logic.common.team.processes.ProcessAddEvent;
import logic.common.team.processes.ProcessFinishEvent;
import logic.common.team.processes.ProcessListener;
import logic.common.team.processes.ProcessStartEvent;
import logic.common.team.processes.ProcessUpdateEvent;
import logic.common.team.processes.TeamProcess;
import logic.common.team.processes.TeamProcessListener;
import logic.common.team.teamListeners.DroneKillEvent;
import logic.common.team.teamListeners.TeamListener;
import logic.common.team.teamListeners.TeamValueChangedEvent;
import logic.common.team.teamListeners.TeamValueType;
import logic.nodes.TeamNode;
import logic.nodes.collision.CollidableNode;
import logic.nodes.collision.ImpulseController;
import logic.nodes.damagable.DamagableNode;
import logic.nodes.lod.blocks.LeafBlock;
import logic.ships.Ship;
import logic.ships.hunter.Hunter;
import logic.ships.mothership.MotherShip;
import logic.status.StatusChangedEvent;
import logic.status.StatusListener;
import logic.weapons.Weapon;
import logic.weapons.WeaponFire;
import logic.weapons.projectileWeapons.heatSeeker.HeatSeeker;
import map.spawnStation.SpawnPosition;
import map.spawnStation.SpawnStation;
import map.spawnStation.stationListeners.SpawnStationPointsListener;
import map.spawnStation.stationListeners.StationPointsChangedEvent;

public class ServerState extends NetworkState implements AbsServerState {
		
	private ServerSyncManager serverManager;
	private GameTimeListener timeListener;
	private Map<Integer, Collection<Short>> weaponFirePlayerMap;
	private short updatingSquadPlayerId;
	
	protected HashMap<Short, TargetSpeedListener> targetSpeedListeners;
	protected HashSet<Hunter> targets;
	
	public ServerState() {
		super("server", true);
	}
	
	@Override
	public void initStructures() {
		super.initStructures();
		
		weaponFirePlayerMap = new HashMap<Integer, Collection<Short>>();
		targetSpeedListeners = new HashMap<Short, TargetSpeedListener>();
		targets = new HashSet<Hunter>();
		
		timeListener = new GameTimeListener() {
			@Override
			public void respawnTimeUpdate() {
				sendTimeSyncMessage(0, TimeType.Spawn);
			}
			
			@Override
			public void gameTimeUpdate(Integer newGameTime) {
				sendTimeSyncMessage(newGameTime.intValue(), TimeType.Game);
			}
			
			@Override
			public void endTimeUpdate(Integer newEndTime) {
				sendTimeSyncMessage(newEndTime.intValue(), TimeType.End);
			}
		};
	}
	
	@Override
	public void update(float tpf) {
		
	}
	
	@Override
	public void initServer(String ip) {
		try {
			serverManager = new ServerSyncManager(ip, this);
			
			for(Synchronizable s : syncHeap) {
				registerSyncObject(s);
			}
			syncHeap.clear();
			
			addListener(ingameState, timeListener);
			
			addTeamListeners(ingameState.getGame().getTeamA());
			addTeamListeners(ingameState.getGame().getTeamB());
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	private void addTeamListeners(Team team) {
		addListener(team, new TeamProcessListener() {
			@Override
			public void processStarted(ProcessStartEvent event) {
				
			}
			
			@Override
			public void processFinished(ProcessFinishEvent event) {
				
			}
			
			@Override
			public void processAdded(ProcessAddEvent event) {
				TeamProcess process = event.getTeamProcess();
				teamProcesses.register(process);
				serverManager.sendMessageToAll(process.getProcessCreateMessage());
				
				addListener(process, new ProcessListener() {
					private float lastPercentage = 0f;
					@Override
					public void processUpdated(ProcessUpdateEvent event) {
						int percentage = event.getPercentage();
						float diff = percentage - lastPercentage;
						if(diff < 2f) return;
						lastPercentage = percentage;
						
						TeamProcess process = event.getTeamProcess();
						Fraction fraction = process.getTeam().getFraction();
						int processID = process.getID();
						ProcessUpdateMessage pum = new ProcessUpdateMessage(fraction, processID, percentage);
						serverManager.sendMessageToAll(pum);
					}
				});
			}
		});
		
		addListener(team, new TeamListener() {
			private int oldMoney = 0;
			@Override
			public void teamValueChanged(TeamValueChangedEvent event) {
				TeamValueType type = event.getType();
				int value = event.getValue();
				if(type == TeamValueType.Money) {
					float diff = FastMath.abs(value - oldMoney);
					if(diff <= 2f) return;
					
					oldMoney = value;
				}
				
				Fraction fraction = event.getTeam().getFraction();
				TeamValueMessage tvm = new TeamValueMessage(fraction, type, value);
				serverManager.sendMessageToAll(tvm);
			}
			
			@Override
			public void playerRemoved(Player player) {}
			@Override
			public void playerAdded(Player player) {}
			@Override
			public void droneDestroyed(DroneKillEvent event) {}
		});
	}
	
	@Override
	public void cleanup() {
		for(Hunter t : targets) {
			t.getListenerContainer(StatusListener.class).cleanUp();
		}
		
		if(serverManager != null) {
			if(ingameState != null && ingameState.getGame() != null && ingameState.getGame().getGameCycle().isOnline()) 
				MasterServerInterface.deleteGame();
			
			serverManager.endServer();
		}
		
		super.cleanup();
		
		serverManager = null;
	}
	
	@Override
	public void connected(JGNConnection connection) {
		
	}
	
	@Override
	public void disconnected(JGNConnection connection) {
		short playershortId = connection.getPlayerId();
		
		Integer playerID = humanPlayersShort.get(Short.valueOf(playershortId));
		if(playerID != null) {
			Player player = getPlayer(playerID.intValue());
			
			if(player != null) {
				sendDisconnectedMessage(player.getName(), playershortId);
				Hunter hunter = player.getHunter();
				if(hunter != null) {
					objects.unregister(hunter.getID());
				}
				
				player.removeHunter();
				player.getTeam().removePlayer(player);
				player.cleanUp();
				players.unregister(playerID.intValue());
				humanPlayers.remove(playerID);
				humanPlayersShort.remove(Short.valueOf(playershortId));
				updatingPlayers.remove(playerID);
			}
		}
	}
	
	@Override
	public void messageCertified(Message message) {
		
	}
	
	@Override
	public void messageFailed(Message message) {
		
	}
	
	@Override
	public void messageReceived(Message message) {
		if(message instanceof JoinMessage) {
			short playerID = message.getPlayerId();
			
			GameStartMessage gsm = new GameStartMessage(ingameState.getGame());
			serverManager.sendMessageToPlayer(gsm, playerID);
			
			sendMSInfoMessage(0, playerID);
			sendMSInfoMessage(1, playerID);
		} else if(message instanceof WeaponRemoveMessage) 
			removeWeaponFromShip(((WeaponRemoveMessage)message).getWeaponIndex());
		else if(message instanceof WeaponAddMessage) 
			createNewWeapon((WeaponAddMessage)message);
		else if(message instanceof WeaponRequestMessage) {
			WeaponRequestMessage wrm = (WeaponRequestMessage)message;
			
			List<Integer> weapons = getWeaponsOfShip(wrm.getShipIndex());
			for(Integer weaponIndex : weapons) {
				WeaponAddMessage wam = new WeaponAddMessage(getWeapon(weaponIndex.intValue()));
				serverManager.sendMessageToPlayer(wam, wrm.getPlayerId());
			}
		} else if(message instanceof WeaponFireAddMessage) {
			WeaponFireAddMessage wfam = (WeaponFireAddMessage)message;
			fireWeapon(wfam, false);
		} else if(message instanceof PlayerRegisterMessage) {
			final PlayerRegisterMessage prm = (PlayerRegisterMessage)message;
			
			sendConnectedMessage(prm.getPlayerName(), prm.getPlayerId());
			
			GameTaskQueueManager.getManager().update(new Callable<Void>() {
				@Override
				public Void call() throws Exception {
					Team team = ingameState.getGame().getTeam(prm.getFraction());
					Team enemyTeam = ingameState.getGame().getTeam(prm.getEnemyFraction());
					
					Player newPlayer = new Player(prm.getPlayerName(), true, team, enemyTeam);
					int newPlayerID = newPlayer.getID();
					Integer newPlayerInteger = Integer.valueOf(newPlayerID);
					Short shortID = Short.valueOf(prm.getPlayerId());
					humanPlayers.put(newPlayerInteger, shortID);
					humanPlayersShort.put(shortID, newPlayerInteger);
					
					PlayerAckMessage packm = new PlayerAckMessage(newPlayerID);
					serverManager.sendMessageToPlayer(packm, prm.getPlayerId());
					
					PlayerUpdateValueType t = PlayerUpdateValueType.SpawnID;
					PlayerAddMessage pam = new PlayerAddMessage(newPlayer);
					for(Integer pID : players.keySet()) {
						if(newPlayerID == pID.intValue()) continue;
						Player p = players.get(pID.intValue());
						
						SpawnPosition spawnPos = p.getCurrentSpawnPosition();
						if(spawnPos == null) continue;
						PlayerUpdateMessage pum = new PlayerUpdateMessage(pID.intValue(), spawnPos.getID(), t);
						serverManager.sendMessageToPlayer(pum, prm.getPlayerId());
					}
					
					serverManager.sendToAllExcept(pam, prm.getPlayerId());
					return null;
				}
			});
		} else if(message instanceof PlayerUpdateMessage) {
			PlayerUpdateMessage pum = (PlayerUpdateMessage)message;
			applyPlayerUpdateMessage(pum);
		} else if(message instanceof NodeSyncMessage) {
			handleNodeSyncMessage((NodeSyncMessage)message);
		} else if(message instanceof UpgradeMessage) {
			handleUpgradeMessage((UpgradeMessage)message);
		} else if(message instanceof TeamChangeWantMessage) {
			TeamChangeWantMessage tcwm = (TeamChangeWantMessage)message;
			Player player = getPlayer(tcwm.getPlayerID());
			boolean answer = ingameState.getGame().canSwitchTeam(player);
			
			TeamChangeAnswerMessage tcam = new TeamChangeAnswerMessage(player.getID(), answer);
			serverManager.sendMessageToAll(tcam);
			
			if(answer) player.switchTeam(player.getEnemyTeam());
		} else if(message instanceof CollisionDamageMessage) {
			applyCollisionDamageMessage((CollisionDamageMessage)message);
		} else if(message instanceof SquadUpdateMessage) {
			applySquadUpdateMessage((SquadUpdateMessage)message);
		} else if(message instanceof CMUseMessage) {
			handleCMUseMessage((CMUseMessage)message);
		} else if(message instanceof ChatMessage) {
			ChatMessage cm = (ChatMessage)message;
			sendChatMessage(cm.getChatText(), cm.getType());
		} else if(message instanceof PingMessage) {
			PingMessage pm = (PingMessage)message;
			long serverTime = System.currentTimeMillis();
			long clientTime = pm.getServerTime();
			int ping = (int)(serverTime - clientTime) / 2;
			Player player = getPlayerFromShort(message.getPlayerId());
			if(player != null) player.setPing(ping);
		}
	}
	
	@Override
	public void registerNewWeapon(Weapon weapon) {
		weapons.register(weapon);
		super.registerNewWeapon(weapon);
	}
	
	@Override
	protected void manageTargetChangedEvent(TargetChangedEvent event) {
		super.manageTargetChangedEvent(event);
		
		Player player = event.getPlayer();
		if(player.isHuman() && player != ingameState.getPlayer()) {
			Hunter oldTarget = event.getOldTarget();
			Hunter newTarget = event.getNewTarget();
			
			if(oldTarget == newTarget) return;
			
			Short shortId = Short.valueOf(getPlayerShortID(player.getID()));
			TargetSpeedListener listener = targetSpeedListeners.get(shortId);
			
			if(listener == null) {
				listener = new TargetSpeedListener(this, newTarget, shortId.shortValue());
				targetSpeedListeners.put(shortId, listener);
			} else listener.setTarget(newTarget);
			
			assert(listener != null);
			
			if(oldTarget != null) {
				oldTarget.removeStatusListener(listener);
				targets.remove(oldTarget);
			}
			
			newTarget.addStatusListener(listener, 0.1f);
			targets.remove(newTarget);
			listener.valueChanged(new StatusChangedEvent(ValueType.Speed, Float.valueOf(newTarget.getCurrentEngineSpeed())));
		}
	}
	
	private void handleCMUseMessage(CMUseMessage cmum) {
		int playerID = cmum.getPlayerID();
		Player player = getPlayer(playerID);
		player.getHunter().useCounterMeasures();
	}
	
	private void applyCollisionDamageMessage(CollisionDamageMessage cdm) {
		int damage = cdm.getDamage();
		Hunter hunter = (Hunter)objects.get(cdm.getHunterID());
		if(hunter.isDestroyed()) return;
		
		int playerID = hunter.getPilot().getID();
		
		addUpdatingPlayer(playerID);
		hunter.receiveDamage(damage);
		removeUpdatingPlayer(playerID);
	}
	
	private void addNodeListeners(final TeamNode node) {
		final int objectID = node.getID();
		final boolean isHunter = node instanceof Hunter;
		StatusListener l = new StatusListener() {
			@Override
			public void valueChanged(StatusChangedEvent event) {
				if(isHunter && pilotIsUpdating(node)) return;
				
				ValueType type = event.getType();
				if(type == ValueType.CounterMeasures 
				|| type == ValueType.Speed 
				|| type == ValueType.Load 
				|| type == ValueType.Resources) return;
				
				Vector3f loc = node.getLocalTranslation();
				NodeSyncMessage nsm = new NodeSyncMessage(objectID, event.getValue(), type, loc);
				serverManager.sendMessageToAll(nsm);
			}
		};
		addStatusListener(node, l, 4f);
		
		if(node instanceof SpawnStation) {
			final SpawnStation station = (SpawnStation)node;
			SpawnStationPointsListener pl = new SpawnStationPointsListener() {
				@Override
				public void pointsChanged(StationPointsChangedEvent event) {
					SpawnStationSyncMessage sssm = new SpawnStationSyncMessage(station, event.getPlayer());
					serverManager.sendMessageToAll(sssm);
				}
			};
			addStatusListener(station, pl, 4f);
		}
	}
	
	@Override
	public boolean isTooFarAway(Vector3f location, short playerId) {
		Player player = getPlayer(humanPlayersShort.get(Short.valueOf(playerId)).intValue());
		if(!player.isAlive()) return true;
		Hunter hunter = player.getHunter();
		if(hunter == null) return true;
		float dist = hunter.getLocalTranslation().distance(location);
		return dist > LeafBlock.UPDATE_DIST;
	}
	
	private boolean pilotIsUpdating(TeamNode node) {
		Hunter hunter = (Hunter)node;
		return isUpdatingPlayer(hunter.getPilot().getID());
	}
	
	@Override
	public void registerStaticObject(TeamNode node) {
		super.registerStaticObject(node);
		addNodeListeners(node);
	}
	
	private void sendMSInfoMessage(int objectID, short playerID) {
		DamagableNode node = objects.get(objectID);
		if(node == null || !(node instanceof MotherShip)) return;
		
		MSInfoMessage msim = new MSInfoMessage((MotherShip)node);
		serverManager.sendMessageToPlayer(msim, playerID);
	}
	
	@Override
	public void messageSent(Message message) {
		
	}
	
	@Override
	public SyncManager getSyncManager() { return serverManager; }
	
	@Override
	public void registerSyncObject(Synchronizable obj) {
		try {
			if(serverManager == null) syncHeap.add(obj);
			else {
				SynchronizeCreateMessage syncMessage = obj.getCreateMessage();
				if(syncMessage == null) return;
				
				if(obj instanceof TeamNode) addNodeListeners((TeamNode)obj);
				
				serverManager.getSyncManager().register(obj, syncMessage, SYNC_UPDATE_RATE);
			}
		} catch(Exception e) { e.printStackTrace(); }
	}
	
	@Override
	public void registerNewShip(Ship ship) {
		assert(ship.getID() == 0);
		objects.register(ship);
	}
	
	@Override
	public void registerTransmittedHunter(Hunter hunter, short playerID) {
		registerNewShip(hunter);
		
		HunterRegisterMessage hrm = new HunterRegisterMessage(hunter.getID());
		serverManager.sendMessageToPlayer(hrm, playerID);
		
		addNodeListeners(hunter);
	}
	
	@Override
	public void registerWeaponFire(int weaponID, WeaponFire fire) {
		registerWeaponFire(fire);
		WeaponFireAddMessage wfam = new WeaponFireAddMessage(weaponID, fire.getID(), fire.getDirection());
		
		Collection<Short> playerShortIDs = new HashSet<Short>();
		for(Short playerId : humanPlayersShort.keySet()) {
			short id = playerId.shortValue();
			if(!isTooFarAway(fire.getShip().getLocalTranslation(), id)) {
				playerShortIDs.add(playerId);
				if(playerId.shortValue() == fire.getWeapon().getClientShortID()) 
					wfam.setupClient(fire.getWeapon().getClientWeaponFireID());
				serverManager.sendMessageToPlayer(wfam, id);
			}
		}
		
		Integer weaponFireID = Integer.valueOf(fire.getID());
		weaponFirePlayerMap.put(weaponFireID, playerShortIDs);
	}
	
	@Override
	public void handleExplosion(WeaponFire fire, CollidableNode other) {
		int fireID = fire.getID();
		
		WeaponFireRemoveMessage wfrm = null;
		
		Vector3f loc = fire.getLocalTranslation().clone();
		if(other != null && other instanceof Hunter) {
			Hunter hunter = (Hunter)other;
			ImpulseController impulseContr = hunter.getNewestImpulseController();
			if(impulseContr == null) wfrm = new WeaponFireRemoveMessage(fireID, loc);
			else {
				int hunterID = hunter.getID();
				float speed = impulseContr.getSpeed();
				Vector3f dir = impulseContr.getMoveDirection();
				wfrm = new WeaponFireRemoveMessage(fireID, hunterID, speed, dir, loc);
			}
		} else wfrm = new WeaponFireRemoveMessage(fireID, loc);
		
		Integer fireIntID = Integer.valueOf(fireID);
		Collection<Short> players = weaponFirePlayerMap.get(fireIntID);
		if(players != null) {
			for(Short playerId : humanPlayersShort.keySet()) {
				if(players.contains(playerId))
					serverManager.sendMessageToPlayer(wfrm, playerId.shortValue());
			}
			
			players.clear();
		}
		
		weaponFirePlayerMap.remove(fireIntID);
	}
	
	@Override
	public void unregisterWeapon(Weapon weapon) {
		super.unregisterWeapon(weapon);
		sendWeaponRemoveMessage(weapon);
	}
	
	@Override
	protected void sendWeaponAddMessage(Weapon weapon) {
		WeaponAddMessage weaponMessage = new WeaponAddMessage(weapon);
		if(serverManager != null) serverManager.sendMessageToAll(weaponMessage);
	}
	
	private void sendWeaponRemoveMessage(Weapon weapon) {
		WeaponRemoveMessage weaponMessage = new WeaponRemoveMessage(weapon.getID());
		serverManager.sendMessageToAll(weaponMessage);
	}
	
	@Override
	public void registerPlayer(Player player) {
		int playerIndex = players.register(player);
		
		addListener(player, createPlayerListener());
		addListener(player, getNewPlayerValueListener(playerIndex));
		addListener(player, createPlayerStationListener(playerIndex));
		
		if(player.isHuman() && ingameState != null && player != ingameState.getPlayer())  {
			addListener(player, new PlayerAwardListener() {
				@Override
				public void playerGotAward(PlayerGotAwardEvent event) {
					int playerID = event.getPlayer().getID();
					PlayerAwardMessage pam = new PlayerAwardMessage(playerID, event.getAward().getAwardType());
					sendMessageTo(pam, getPlayerShortID(playerID));
				}
			});
		}
	}
	
	public void sendTimeSyncMessage(int time, TimeType type) {
		TimeSyncMessage m = new TimeSyncMessage(time, type);
		serverManager.sendMessageToAll(m);
	}
	
	@Override
	public void sendPlayerUpdateMessage(PlayerUpdateMessage pum) {
		int playerID = pum.getPlayerID();
		Player player = getPlayer(playerID);
		
		boolean isThisPlayer = ingameState.getPlayer() == player;
		if(!isThisPlayer && player.isHuman() && pum.getType() == PlayerUpdateValueType.Respawn) {
			short id = humanPlayers.get(Integer.valueOf(playerID)).shortValue();
			serverManager.sendToAllExcept(pum, id);
		} else serverManager.sendMessageToAll(pum);
	}
	
	@Override
	protected void sendUpgradeMessage(UpgradeMessage um) {
		serverManager.sendMessageToAll(um);
	}
	
	@Override
	protected SquadListener getSquadListener(Squad squad) {
		return new SquadListener() {
			@Override
			public void squadChanged(SquadChangedEvent event) {
				Squad squad = event.getSquad();
				SquadUpdateMessage sum = new SquadUpdateMessage(squad);
				
				for(Short playerId : humanPlayersShort.keySet()) {
					if(playerId.shortValue() != updatingSquadPlayerId) 
						serverManager.sendMessageToPlayer(sum, playerId.shortValue());
				}
			}
		};
	}
	
	@Override
	protected void updateSquad(Team team, SquadUpdateMessage sum, Collection<Player> newMembers) {
		updatingSquadPlayerId = sum.getPlayerId();
		super.updateSquad(team, sum, newMembers);
		updatingSquadPlayerId = -1;
	}
	
	@Override
	public void unregisterWeaponFire(WeaponFire fire) {
		super.unregisterWeaponFire(fire);
		if(fire instanceof HeatSeeker) unregisterObject((HeatSeeker)fire);
	}
	
	@Override
	public void unregisterObject(Synchronizable obj) {
		serverManager.getSyncManager().unregister(obj);
		super.unregisterObject(obj);
	}
	
	@Override
	public void sendCMUseMessage(int playerID) {
		Vector3f loc = getPlayerHunterLoc(playerID);
		CMUseMessage cmum = new CMUseMessage(playerID, loc);
		serverManager.sendMessageToAll(cmum);
	}
	
	@Override
	public void sendChatMessage(String text, ChatType type) {
		ChatMessage cm = createChatMessage(text, type);
		serverManager.sendMessageToAll(cm);
		applyChatMessage(cm);
	}
	
	private void sendDisconnectedMessage(String playerName, short playerID) {
		String text = "Server: " + playerName + " disconnected";
		ChatMessage cm = createChatMessage(text, ChatType.Chat);
		serverManager.sendToAllExcept(cm, playerID);
		applyChatMessage(cm);
	}
	
	private void sendConnectedMessage(String playerName, short playerID) {
		String text = "Server: " + playerName + " connected";
		ChatMessage cm = createChatMessage(text, ChatType.Chat);
		serverManager.sendToAllExcept(cm, playerID);
		applyChatMessage(cm);
	}

	@Override
	protected void handleNearSpawnStationChanged(NearStationChangedEvent event, int playerID) {
		NearStationChangeMessage nscm = new NearStationChangeMessage(playerID, event.getSpawnPos());
		serverManager.sendMessageToAll(nscm);
	}
	
	@Override
	protected void handleKillEvent(KillEvent event) {
		int killerID = event.getKiller().getID();
		int victimID = event.getVictim().getID();
		PlayerKillMessage pkm = new PlayerKillMessage(killerID, victimID, event.getWeapon().getID());
		serverManager.sendMessageToAll(pkm);
	}
	
	@Override
	public void sendMessageTo(SimpleMessage message, short playerId) {
		serverManager.sendMessageToPlayer(message, playerId);
	}
	
	@Override
	public void sendPingMessage(short playerId) {
		sendMessageTo(new PingMessage(System.currentTimeMillis()), playerId);
	}
}